package fem2;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import math2.GeometryUtilities;
import math2.MathUtilities;

/**
 * class represent topology of a mesh (node-element relation, checking boundary,
 * ...)
 * 
 * @author hbui
 * 
 */
public class Topology {

	/*
	 * variable to store set of element contains the node
	 */
	private Map<Node, Set<Element>> nodeToElementMapping = new TreeMap<Node, Set<Element>>();

	/*
	 * variable contains boundary nodes
	 */
	private Set<Node> boundaryNodes = new TreeSet<Node>();

	public Topology(Model m) {
		this.buildTopology(m);
	}

	private void buildTopology(Model m) {

		/*
		 * for each element check for all nodes contained in that element and
		 * associate the nodes with that element
		 */
		for (int i = 0; i < m.countElements(); i++) {
			Element e = m.getElement(i);
			Node[] nodes = e.getMeshPart().getNodes();

			for (Node n : nodes) {

				/*
				 * initialize the element container if it doesn't exist
				 */
				if (nodeToElementMapping.get(n) == null) {
					Set<Element> elementSet = new TreeSet<Element>();
					nodeToElementMapping.put(n, elementSet);
				}

				/*
				 * associate the node with element
				 */
				Set<Element> elementSet = nodeToElementMapping.get(n);
				elementSet.add(e);
			}
		}

		/*
		 * build the boundary nodes
		 */
		Mesh mesh = m.getMesh();
		for (int i = 0; i < mesh.countNodes(); i++) {
			Node n = mesh.getNode(i);
			if (isBoundary(n)) {
				boundaryNodes.add(n);
			}
		}
	}

	/**
	 * get set of elements contain the node
	 * 
	 * @param n
	 * @return
	 */
	public Set<Element> getNeighbourElements(Node n) {
		return nodeToElementMapping.get(n);
	}

	/**
	 * get set of nodes on the boundary
	 * 
	 * @return
	 */
	public Set<Node> getBoundaryNodes() {
		return boundaryNodes;
	}

	/**
	 * check if a node is a boundary node by checking the local angle of node in
	 * each neightbour element
	 * 
	 * @param n
	 * @return
	 */
	private boolean isBoundary(Node n) {
		Set<Element> elements = getNeighbourElements(n);
		double angle = 0.0;
		for (Element e : elements) {
			MeshPart mp = e.getMeshPart();
			int i = mp.contains(n);
			int[] ni = MeshPart.getNeighbouringIndex(mp.getType(), i);
			Node[] nodes = mp.getNodes();

			double[] p = n.getPosition();
			double[] p1 = nodes[ni[0]].getPosition();
			double[] p2 = nodes[ni[1]].getPosition();

			angle += GeometryUtilities.computeAngle(p, p1, p2);
		}
		return Math.abs(angle - 2 * Math.PI) > MathUtilities.tol6;
	}
}
